package controler.algorithms.bayesian.gmm.model;

import controler.algorithms.Algorithm;
import Jama.Matrix;

public class NormalDistribution implements Cloneable{
	Matrix covariancesMatrix;
	Matrix meansMatrix;
	int dimension;
	
	/**
	 * Konstruuje rozklad normalny bez wyznaczonych parametrow macierzy kowariancji i srednich
	 */
	public NormalDistribution()
	{		
	}	
	/**Konstruuje rozklad normalny biorac za macierz srednich losowy wektor cech ze zbioru uczacego.
	 * Macierz kowariancji estymowana jest na calym zbiorze uczacym.
	 * 
	 * @param dataSet - zbior uczacy
	 */
	public NormalDistribution(double [][] dataSet)
	{		
		//random inizialization
		int i = (int)(Math.random()*dataSet.length);
		meansMatrix = new Matrix(dataSet[i], dataSet[0].length);
			
		estimateCovariance(dataSet);
	}
	/**
	 * Konstruuje rozklad normalny biorac za macierz srednich losowy wektor cech ze zbioru uczacego.
	 * Macierz kowariancji estymowana jest na calym zbiorze uczacym.
	 * 
	 * @param dataSet - zbior uczacy
	 * @param j - liczba porzadkowa komponentu rozkladu zawierajacego ten rozklad
	 * @param k - liczba komponentow mieszaniny rozkladow
	 */
	public NormalDistribution(double [][] dataSet, int j, double k)
	{		
		//random inizialization
		double w = 1./k;
		double r = Math.random();
		while(r > w*(double)(j+1) || r < w*(double)j)
			r = Math.random();
		
		int i = (int)(r*dataSet.length);
		meansMatrix = new Matrix(dataSet[i], dataSet[0].length);
			
		estimateCovariance(dataSet);
	}
	
	
	/**Konstruuje rozklad normalny biorac za macierz srednich oraz macierz kowariancji macierze
	 * podane jako argumenty.
	 * 
	 * @param covariancesMatrix - macierz kowariancji
	 * @param meansMatrix - macierz srednich
	 */
	public NormalDistribution(Matrix covariancesMatrix, Matrix meansMatrix)
	{
		this.covariancesMatrix = covariancesMatrix;
		this.meansMatrix = meansMatrix;
	}
	/**
	 * Zwraca rozklad brzegowy dla podanego wektora wejsciowegp
	 * @param dataSet - wektor wybranych cech wejsciowych
	 * @return wartosc prawdopodobienstwa rozkladu brzegowego dla podanych cech
	 */
	public double getMarginalDistributionValue(double [] x)
	{
		Matrix covariance = covariancesMatrix.getMatrix(0, covariancesMatrix.getRowDimension()-2, 0, covariancesMatrix.getColumnDimension()-2);
		Matrix means = meansMatrix.getMatrix(0, meansMatrix.getRowDimension()-2, 0, 0);
		
		return getProbability(x, means, covariance);
	}
	/**Zwraca wartosc srednia rozkladu warunkowego wartosc predykowana
	 * pod warunkiem zaistenienia pozostalych cech
	 * 
	 * @param x - wartosc
	 * @return wartosc oczekiwana rozkladu warunkowego
	 */
	public double getConditionalMeans(double [] x)
	{
		Matrix covarianceEE = covariancesMatrix.getMatrix(0, covariancesMatrix.getRowDimension()-2, 0, covariancesMatrix.getColumnDimension()-2);
		covarianceEE = covarianceEE.inverse();
		Matrix covarianceFE = covariancesMatrix.getMatrix(covariancesMatrix.getRowDimension()-1, covariancesMatrix.getRowDimension()-1 , 0, covariancesMatrix.getRowDimension()-2);
		Matrix meansE = meansMatrix.getMatrix(0, meansMatrix.getRowDimension()-2, 0, 0);
		Matrix meansF = meansMatrix.getMatrix(meansMatrix.getRowDimension()-1, meansMatrix.getRowDimension()-1, 0, 0);
		Matrix xe = new Matrix(x, x.length);
		
		Matrix temp = meansE.minus(xe);

		Matrix result = covarianceFE.times(covarianceEE);
		result = result.times(temp);
		result = meansF.minus(result);
	
		return result.get(0, 0);
	}
	/**Zwraca wartosc prawdopodbienstwa rozkladu dla danych wektorow
	 * wejsciowych
	 * 
	 * @param x - wektor cech
	 * @return wartosc prawdopodobienstwa dla podanego wektora cech
	 */
	public double getProbability(double [] x)
	{	
		return getProbability(x, this.meansMatrix, this.covariancesMatrix);
	}
	private double getProbability(double [] x, Matrix meansMatrix, Matrix covariancesMatrix)
	{
		Matrix X = new Matrix(x, x.length);
		double param1 = Math.pow(Math.sqrt(Math.PI), dimension);
		param1 *= Math.sqrt(covariancesMatrix.det());
		
		param1 = 1./param1;
		Matrix temp = X.minus(meansMatrix);
		
		double param2 = temp.transpose().times(covariancesMatrix.inverse()).times(temp).get(0, 0);
		param2 *= -(1./2.);
		
		double result = Math.exp(param2)*param1;	
		
		if(result == 0);
			//result = Double.MIN_VALUE;
		
		return result;
	}
	/**Dopasowuje parametry komponentu optymalnie do danych wejsciowych. Estymuje macierze srednich i 
	 * kowariancji.
	 * @param dataSet - dane wejsciowe
	 * @param weights - wagi modelu
	 */
	public void estimateParams(double [][] dataSet, double [] weights)
	{
		estimateMeans(dataSet, weights);
		estimateCovariance(dataSet, weights);       
	}
	/**Dopasowuje parametry komponentu optymalnie do danych wejsciowych. Estymuje macierze srednich i 
	 * kowariancji.
	 * @param dataSet - dane wejsciowe
	 * 
	 */
	public void estimateParams(double [][] dataSet)
	{
		estimateMeans(dataSet);
		estimateCovariance(dataSet);       
	}
	private void estimateMeans(double [][] dataSet)
	{
		int rows = dataSet.length;
        int cols = dataSet[0].length;
               
        double[] mean = new double[cols];

        for (int i = 0; i < rows; i++)
        {
            double[] row = dataSet[i];
            
            for (int j = 0; j < cols; j++)
                mean[j] += row[j];
        }
        
        for (int j = 0; j < cols; j++)
        {
            mean[j] /= (double)dataSet.length;
        }
        
        meansMatrix = new Matrix(mean, mean.length);  
	}
	 private void estimateCovariance(double[][] dataSet)
     {
         int rows = dataSet.length;
         int cols = dataSet[0].length;
                  
         double [] means = meansMatrix.getColumnPackedCopy();         
         double[][] cov = new double[cols][cols];
    	 
         for (int i = 0; i < cols; i++)
    	     for (int j = i; j < cols; j++)
    	     {
    	    	 double s = 0.0;
    	    	 for (int k = 0; k < rows; k++)
    	    		 s += (dataSet[k][j] - means[j]) * (dataSet[k][i] - means[i]);
    	    	 s /= ((double)dataSet.length - 1.);
    	    	 
    	    	 cov[i][j] = s;
    	    	 cov[j][i] = s;
    	     }
    	 
         covariancesMatrix = new Matrix(cov, cov.length, cov.length);

     }
	
	private void estimateMeans(double [][] dataSet, double [] weights)
	{
		int rows = dataSet.length;
        int cols = dataSet[0].length;
               
        double[] mean = new double[cols];

        for (int i = 0; i < rows; i++)
        {
            double[] row = dataSet[i];
            double w = weights[i];
            
            for (int j = 0; j < cols; j++)
                mean[j] += row[j] * w;
        }
        
        meansMatrix = new Matrix(mean, mean.length);  
	}
	 private void estimateCovariance(double[][] dataSet, double[] weights)
     {
         int rows = dataSet.length;
         int cols = dataSet[0].length;
         
         double divisor = 1.0;
         for (int i = 0; i < weights.length; i++)
             divisor -= weights[i] * weights[i];
         
         double [] means = meansMatrix.getColumnPackedCopy();         
         double[][] cov = new double[cols][cols];
    	 
         for (int i = 0; i < cols; i++)
    	     for (int j = i; j < cols; j++)
    	     {
    	    	 double s = 0.0;
    	    	 for (int k = 0; k < rows; k++)
    	    		 s += weights[k] * (dataSet[k][j] - means[j]) * (dataSet[k][i] - means[i]);
    	    	 s /= divisor;
    	    	 cov[i][j] = s;
    	    	 cov[j][i] = s;
    	     }
    	 
         covariancesMatrix = new Matrix(cov, cov.length, cov.length);
     }
	/**Zwraca macierz kowariancji
	 * 
	 * @return macierz kowariancji
	 */
	public Matrix getCovariancesMatrix() {
		return covariancesMatrix;
	}

	/**
	 * @param covariancesMatrix -  macierz kowariancji
	 */
	public void setCovariancesMatrix(Matrix covariancesMatrix) {
		this.covariancesMatrix = covariancesMatrix;
	}

	/**Zwraca macierz srednich
	 * 
	 * @return macierz srednich
	 */
	public Matrix getMeansMatrix() {
		return meansMatrix;
	}

	/**
	 * @param meansMatrix - macierz srednich
	 */
	public void setMeansMatrix(Matrix meansMatrix) {
		this.meansMatrix = meansMatrix;
	}

	/**Zwraca liczbe wymiarow modelu
	 * 
	 * @return liczba wymiarow rozkladu
	 */
	public int getDimension() {
		return dimension;
	}

	/**
	 * 
	 * @param dimension - liczba wymiarow rozkladu
	 */
	public void setDimension(int dimension) {
		this.dimension = dimension;
	}	
	public NormalDistribution clone() throws CloneNotSupportedException {
		NormalDistribution result = (NormalDistribution) super.clone();
		result.setCovariancesMatrix((Matrix)covariancesMatrix.clone());
		result.setMeansMatrix((Matrix)meansMatrix.clone());
        
		return result;
	}
}
